package componentes.basicos;



/**
 * Classe que representa a ALU (Arithmetic and Logic Unit).
 * Essa classe é responsável por realizar as operações aritméticas e lógicas
 * sobre dois dados de entrada. A operação é definida a partir de um sinal de controle.
 * 
 * @author Gustavo Miguel, Cleisson Alves, Evandro Francisco, Lucas.
 */
public class ALU {
    
    /* Atributos da Classe */
    private Barramento entrada1;
    private Barramento entrada2;
    private Barramento resultado;
    private Barramento sinalControle;
    
    // Sinais de controle para overflow, carry, e zero
    private int flagZ; // Zero.
    private int flagV; // Overflow.
    
    private static char vaiUm;  // Vai um usado para calcula o complemento de dois.
    
    /**
     * Cria uma Unidade Lógica Aritmética padrão. Um objeto dessa classe, representa
     * uma ALU padrão, onde todos os barramentos conectados à ela são nulos.
     * @see Barramento
     */
    public ALU() {
        vaiUm = '0';
        flagZ = 0;
        flagV = 0;
        this.entrada1 = new Barramento(new Palavra());
        this.entrada2 = new Barramento(new Palavra());
        this.resultado = new Barramento(new Palavra());
        this.sinalControle = new Barramento(new Palavra());
    }
    
    /**
     * Retorna o Barramento da primeira entrada da ALU.
     * @return Barramento correspondente a primeira entrada da ALU.
     */
    public Barramento getEntrada1() {
        return entrada1;
    }

    
    /**
     * Retorna o Barramento da segunda entrada da ALU.
     * @return Barramento correspondente a segunda entrada da ALU.
     */
    public Barramento getEntrada2() {
        return entrada2;
    }

    
    /**
     * Retorna sinal indicando se o resultado de uma operação realizada pela ALU
     * sobre duas entradas gerou um overflow. 
     * @return Retorna 1 ser o resultado obtido gerou overflow. Retorna 0 caso contrário.
     */
    public int getFlagV() {
        return flagV;
    }

    /**
     * Retorna sinal indicando se o resultado de uma operação realizada pela ALU
     * sobre duas entradas foram iguais a zero. 
     * @return Retorna 1 ser o resultado obtido é zero. Retorna 0 caso contrário.
     */
    public int getFlagZ() {
        return flagZ;
    }

    /**
     * Método para pegar o resultado do Barramento correspondente ao resultado da ALU.
     * O resultado é obtido a partir de uma operação feita sobre as entradas da ALU.
     * @return Resultado obtido pela ALU sobre suas entradas.
     */
    public Barramento getResultado() {
        return resultado;
    }

    
    /**
     * Retorna o sinal de controle correspondente. O sinal de controle é usado 
     * pela ALU para indicar quais das operações será realizadas sobre suas entradas.
     * @return Retorna o Barramento correspondente ao sinal de controle da ALU.
     */
    public Barramento getSinalControle() {
        return sinalControle;
    }

    /**
     * Configura um barramento de entrada para ALU. O Barramento especifica
     * os valores que a entrada da ALU irá conter.
     * @param entrada1 Barramento para a primeira entrada da ALU.
     */
    public void setEntrada1(Barramento entrada1) {
        this.entrada1 = entrada1;
    }

     /**
     * Configura um barramento de entrada para ALU. O Barramento especifica
     * os valores que a entrada da ALU irá conter.
     * @param entrada2 Barramento para a segunda entrada da ALU.
     */
    public void setEntrada2(Barramento entrada2) {
        this.entrada2 = entrada2;
    }

    /**
     * Configura o sinal de controle que indica se o resultado sobre as
     * entradas da ALU resultaram em um overflow.
     * @param flagV Valor do sinal de controle.
     */
    public void setFlagV(int flagV) {
        this.flagV = flagV;
    }

    /**
     * Configura o sinal de controle que indica se o resultado sobre as 
     * entradas da alu resultaram no valor zero.
     * @param flagZ Valor do sinal de controle.
     */
    public void setFlagZ(int flagZ) {
        this.flagZ = flagZ;
    }

    /**
     * Configura o Barramento que corresponde ao sinal de controle da ALU.
     * O Sinal de controle da ALU especifica qual a operação que ela irá 
     * realizar sobre suas entradas.
     * @param sinalControle Valor do sinal de controle.
     */
    public void setSinalControle(Barramento sinalControle) {
        this.sinalControle = sinalControle;
    }
    
    /**
     * Método para configura o resultado da ALU. O Resultado da ALU é obtido 
     * a partir de suas duas entradas e de seu sinal de controle. A partir do
     * resultado também pode ser configurado os sinais de controle que indicam
     * se a operação efetuada resultou em um valor zero e/ou em um overflow.
     */
    public void setResultado() {
        
        int valorEntrada1 = entrada1.getBitsValor();
        int valorEntrada2 = entrada2.getBitsValor();
        int valorResultado = 0;
        
        // Realiza a operação sobre as entradas a partir do sinal de controle.
        if(sinalControle.getPalavra().palavrasSaoIguais(Constantes.ADD)){
            valorResultado = valorEntrada1 + valorEntrada2;
            resultado.setBitsValor(valorResultado);
        } else if (sinalControle.getPalavra().palavrasSaoIguais(Constantes.SUB)) {
            valorResultado = valorEntrada1 -  valorEntrada2;
            resultado.setBitsValor(valorResultado);
        } else if (sinalControle.getPalavra().palavrasSaoIguais(Constantes.OR)) {
            valorResultado = valorEntrada1 |  valorEntrada2;
            resultado.setBitsValor(valorResultado);
        } else if (sinalControle.getPalavra().palavrasSaoIguais(Constantes.AND)) {
            valorResultado = valorEntrada1 &  valorEntrada2;
            resultado.setBitsValor(valorResultado);
        } else {
            if(valorEntrada1 < valorEntrada2)
                resultado.setBitsValor(1);
            else
                resultado.setBitsValor(0);
        }
        
        // Configura os valores dos sinais de controle.
        // Configura o sinal de controle que indica se o resultado é zero.
        if(valorResultado == 0) {
            setFlagZ(1);
        }
        
        // Configura o sinal de controle que indica se o resultado gerou um overflow.
        if(valorResultado > Integer.MAX_VALUE) {
            setFlagV(1);
        }
        
    }   
    
    /**
     * Método estático para calcular o complemento de dois de uma sequência de bits.
     * @param bits Sequencia de bits.
     * @return String com o complemento de dois correspondente da entrada bits.
     */
    public static String complemento2(String bits) {

        String um = extend("01", bits.length());
        return add(invert(bits), um);
    }
    
    /* Metodo privados para auxiliar no calculo do complemento de dois.*/
    private static String invert(String bits) {
        char[] c1 = bits.toCharArray();
        String c3 = "";
        for (int i = 0; i < bits.length(); ++i) {
            if (c1[i] == '0') {
                c3 +='1';
                continue;
            }
            if (c1[i] == '1') {
                c3+= '0';
                continue;
            }
        }
        return c3;
    }
    
    public static  String add(String bits1, String bits2) {
        char[] c1 = bits1.toCharArray();
        char[] c2 = bits2.toCharArray();
        if (c2.length != c1.length) {
            throw new IllegalArgumentException("String de bits com tamanhos distintos.");
        }
        String c3 = "";
        for (int i = c1.length - 1; i >= 0; --i) {
            if (c1[i] == '0' && c2[i] == '0' && vaiUm == '0') {
                c3 = '0' + c3;
                vaiUm = '0';
                continue;
            }
            if (c1[i] == '0' && c2[i] == '0' && vaiUm == '1') {
                c3 = '1' + c3;
                vaiUm = '0';
                continue;
            }
            if (c1[i] == '0' && c2[i] == '1' && vaiUm == '0') {
                c3 = '1' + c3;
                vaiUm = '0';
                continue;
            }
            if (c1[i] == '0' && c2[i] == '1' && vaiUm == '1') {
                c3 = '0' + c3;
                vaiUm = '1';
                continue;
            }
            if (c1[i] == '1' && c2[i] == '0' && vaiUm == '0') {
                c3 = '1' + c3;
                vaiUm = '0';
                continue;
            }
            if (c1[i] == '1' && c2[i] == '0' && vaiUm == '1') {
                c3 = '0' + c3;
                vaiUm = '1';
                continue;
            }
            if (c1[i] == '1' && c2[i] == '1' && vaiUm == '0') {
                c3 = '0' + c3;
                vaiUm = '1';
                continue;
            }
            if (c1[i] == '1' && c2[i] == '1' && vaiUm == '1') {
                c3 = '1' + c3;
                vaiUm = '1';
                continue;
            }
        }
        return c3;
    }
    
    private static String extend(String bits, int tam) {
        char bit;
        bit = bits.charAt(0);
        for (int i = bits.length(); i < tam; ++i) {
            bits = bit + bits;
        }
        return bits;
    }
    
    
    
} // Fim da classe.
